package com.california.pay.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.bwton.socket.exception.SocketAbstractException;
import com.bwton.socket.transport.call.Request;
import com.bwton.socket.transport.call.Response;
import com.bwton.socket.transport.call.SocketRequest;
import com.bwton.socket.util.RemotingUtil;
import com.california.pay.common.id.IdWorker;
import com.california.pay.common.utils.DateUtils;
import com.california.pay.common.utils.MD5Sign;
import com.california.pay.config.CommonConstants;
import com.california.pay.config.CommonErrors;
import com.california.pay.config.ServerConfig;
import com.california.pay.consts.NotifyStatus;
import com.california.pay.consts.OrderStatus;
import com.california.pay.consts.Status;
import com.california.pay.exception.BusinessException;
import com.california.pay.model.PersonOrderPayReq;
import com.california.pay.model.PersonOrderPayRsp;
import com.california.pay.persist.domain.MchPayQuota;
import com.california.pay.persist.domain.PayOrder;
import com.california.pay.persist.domain.PayPersonMch;
import com.california.pay.persist.domain.TerminalLogin;
import com.california.pay.persist.mapper.MchPayQuotaMapper;
import com.california.pay.persist.mapper.PayOrderMapper;
import com.california.pay.persist.mapper.PayPersonMchMapper;
import com.california.pay.persist.mapper.TerminalLoginMapper;
import com.california.pay.socket.NettyChannelMap;
import com.california.pay.socket.ProviderClientService;
import com.california.pay.socket.TransSocketMessage;
import com.google.common.collect.Maps;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class OrderPayService extends BaseService {
    @Autowired
    private PayOrderMapper payOrderMapper;
    @Autowired
    private IdWorker idWorker;
    @Autowired
    private TerminalLoginService loginService;
    @Autowired
    private TerminalLoginMapper loginMapper;
    @Autowired
    private PayPersonMchMapper payPersonMchMapper;
    @Autowired
    private ProviderClientService providerClientService;
    @Autowired
    private MchPayQuotaMapper payQuotaMapper;


    /**
     * 订单超时时间
     */
    private int payOrderExpireTime = ServerConfig.propertiesLoader.getInteger("pay.order.expire.time");
    /**
     * 终端校验超时时间
     */
    private int checkTerminalValidTimeout = ServerConfig.propertiesLoader.getInteger("check.terminal.valid.timeout", 3000);

    @Transactional(rollbackFor = {Exception.class})
    public PersonOrderPayRsp personMchOrderPay(PersonOrderPayReq orderPayReq) {
        validate(orderPayReq);
        checkExistPayOrder(orderPayReq.getInnerMchNo(), orderPayReq.getOutOrderNo());
        Long payAmount = orderPayReq.getAmount();
        TerminalLogin terminalLogin = getNextTerminalLogin(payAmount);
        int i = 0;

        while (!checkTerminalStatus(terminalLogin)) {
            terminalLogin = getNextTerminalLogin(payAmount);
            if (++i > 3) {
                throw new BusinessException(CommonErrors.UNDEFINED, "未找到可用的终端设备");
            }
        }

        PayPersonMch personMch = payPersonMchMapper.selectByPrimaryKey(terminalLogin.getChannelMchId());
        PayOrder payOrder = createPayOrder(orderPayReq, personMch, terminalLogin);
        return buildPersonOrderPayRsp(orderPayReq, payOrder);
    }

    private TerminalLogin getNextTerminalLogin(Long payAmount) {
        Date now = new Date();
        Long payDate = Long.parseLong(DateUtils.yyyyMMdd(now));

        Channel channel = NettyChannelMap.getNextChannel();
        String nettyChannelId = channel.id().asLongText();

        TerminalLogin terminalLogin = loginMapper.selectOneByChannelId(nettyChannelId);

        // 获取登录终端设备未空，强制下线
        if (terminalLogin == null) {
            loginService.logout(channel, nettyChannelId);
            channel.close();
            return getNextTerminalLogin(payAmount);
        }

        PayPersonMch payPersonMch = payPersonMchMapper.selectByPrimaryKey(terminalLogin.getChannelMchId());
        MchPayQuota mchPayQuota = payQuotaMapper.selectOneByMchIdAndPayDate(payDate, terminalLogin.getChannelMchId());
        // 已超出金额，强制下线
        if (mchPayQuota.getPayTotalAmount() > payPersonMch.getMaxAmount() + payAmount) {
            if (mchPayQuota.getPayTotalAmount() >= payPersonMch.getMaxAmount()) {
                loginService.logout(channel, nettyChannelId);
                channel.close();
            }
            return getNextTerminalLogin(payAmount);
        }

        return terminalLogin;
    }

    private PersonOrderPayRsp buildPersonOrderPayRsp(PersonOrderPayReq orderPayReq, PayOrder payOrder) {
        PersonOrderPayRsp rsp = new PersonOrderPayRsp();
        rsp.setAmount(orderPayReq.getAmount());
        rsp.setOutOrderNo(orderPayReq.getOutOrderNo());
        rsp.setInnerOrderNo(payOrder.getInnerOrderNo());
        rsp.setChannelUserId(payOrder.getChannelUserId());
        rsp.setSystemTime(LocalDateTime.now());
        return rsp;
    }

    private MchPayQuota getMinPayAmountRecord(Long payAmount) {
        Date now = new Date();
        Long payDate = Long.parseLong(DateUtils.yyyyMMdd(now));
        Long minAmount = payQuotaMapper.getMinPayAmount(payDate, payAmount);

        if (minAmount == null) {
            throw new BusinessException(CommonErrors.UNDEFINED, "未发现有可用(金额)的终端");
        }

        MchPayQuota quota = payQuotaMapper.getMinPayAmountRecord(payDate, minAmount);
        if (quota == null) {
            throw new BusinessException(CommonErrors.UNDEFINED, "未发现有登录的终端");
        }

        return quota;
    }

    @Transactional(rollbackFor = {Exception.class})
    public PayOrder createPayOrder(PersonOrderPayReq orderPayReq, PayPersonMch personMch, TerminalLogin terminalLogin) {
        Date now = new Date();
        PayOrder payOrder = new PayOrder();
        payOrder.setOrderId(idWorker.nextIdString());
        payOrder.setInnerOrderNo(idWorker.nextIdString());
        payOrder.setInnerMchNo(orderPayReq.getInnerMchNo());
        payOrder.setMchOrderNo(orderPayReq.getOutOrderNo());
        payOrder.setTotalAmount(orderPayReq.getAmount());
        payOrder.setCurrency("156");
        payOrder.setOrderStatus(OrderStatus.ORDER_GEN);
        payOrder.setClientIp(orderPayReq.getClientIp());
        //payOrder.setDevice("");
        payOrder.setTerminalId(terminalLogin.getTerminalId());
        payOrder.setGoodsName(orderPayReq.getGoodsName());
        payOrder.setGoodsDesc(orderPayReq.getGoodsDesc());
        //payOrder.setExtra("");
        payOrder.setChannelMchId(personMch.getPersonMchId());
        payOrder.setChannelUserId(personMch.getChannelUserId());
        //payOrder.setChannelAppId("");
        //payOrder.setChannelOrderNo("");
        //payOrder.setChannelErrCode("");
        //payOrder.setChannelErrMsg("");
        payOrder.setChannelLastNotifyTime(0L);
        //payOrder.setParam1("");
        //payOrder.setParam2("");
        payOrder.setMchNotifyUrl(orderPayReq.getNotifyUrl());
        payOrder.setMchReturnUrl(orderPayReq.getReturnUrl());
        payOrder.setMchNotifyCount(0);
        payOrder.setMchNotifyStatus(NotifyStatus.NONE);
        payOrder.setMchReturnStatus(NotifyStatus.NONE);
        payOrder.setLastMchNotifyTime(0L);
        //payOrder.setRemark("");
        payOrder.setPaySuccTime(0L);

        long nowTime = Long.parseLong(DateUtils.yyyyMMddHHmmss(now));
        payOrder.setOrderGenTime(nowTime);
        payOrder.setOrderExpireTime(Long.parseLong(DateUtils.yyyyMMddHHmmss(DateUtils.addMinute(now, payOrderExpireTime))));

        payOrder.setCreateTime(now);
        payOrder.setUpdateTime(now);
        payOrderMapper.insertSelective(payOrder);
        return payOrder;
    }

    private void checkExistPayOrder(String innerMchNo, String mchOrderNo) {
        PayOrder payOrder = getPayOrder(innerMchNo, mchOrderNo);
        if (payOrder != null) {
            throw new BusinessException(CommonErrors.UNDEFINED, "该商户已提交该订单，请勿再提交");
        }
    }

    public PayOrder getPayOrder(String innerMchNo, String outOrderNo) {
        PayOrder query = new PayOrder();
        query.setMchOrderNo(outOrderNo);
        query.setInnerMchNo(innerMchNo);

        PayOrder payOrder = payOrderMapper.selectOne(query);
        return payOrder;
    }

    public PayOrder getPayOrder(String innerOrderNo) {
        if (StringUtils.isBlank(innerOrderNo)) {
            throw new BusinessException(CommonErrors.UNDEFINED, "平台支付订单号不能为空");
        }
        PayOrder query = new PayOrder();
        query.setInnerOrderNo(innerOrderNo);

        PayOrder payOrder = payOrderMapper.selectOne(query);
        return payOrder;
    }

    public TerminalLogin getTerminalLogin(MchPayQuota payQuota, Long payAmount) {
        String channelMchId = payQuota.getChannelMchId();
        TerminalLogin query = new TerminalLogin();
        query.setChannelMchId(channelMchId);
        List<TerminalLogin> list = loginMapper.select(query);

        if (list == null || list.size() == 0) {
            MchPayQuota updateQuota = new MchPayQuota();
            updateQuota.setQuotaId(payQuota.getQuotaId());
            updateQuota.setStatus(Status.DISABLE);
            payQuotaMapper.updateByPrimaryKeySelective(updateQuota);
            payQuota = getMinPayAmountRecord(payAmount);

            return getTerminalLogin(payQuota, payAmount);
        }

        return list.get(0);
    }

    private boolean checkTerminalStatus(TerminalLogin terminalLogin) {
        TransSocketMessage reqMessage = new TransSocketMessage();
        reqMessage.setAppId("000000");
        reqMessage.setVersion("0001");
        reqMessage.setSessionId(terminalLogin.getSessionId());
        reqMessage.setSeq(idWorker.nextIdString());
        reqMessage.setCommandCode("A1");
        reqMessage.setTimestamp(System.currentTimeMillis() + "");
        reqMessage.setReturnCode("00");
        reqMessage.setSignMethod("md5");

        Map<String, String> msgMap = Maps.newHashMap();
        msgMap.put("clientId", terminalLogin.getClientId());
        msgMap.put("channelUserId", terminalLogin.getChannelUserId());
        String message = JSON.toJSONString(msgMap);
        reqMessage.setMessage(message);

        String originBody = reqMessage.getOriginBody();
        String signBody = originBody + "appKey=" + terminalLogin.getAppKey();
        log.info(">>>>>>>>>>>>请求校验终端报文，采用签名appKey={}, loginId={}", terminalLogin.getAppKey(), terminalLogin.getLoginId());
        String sign = MD5Sign.md5(signBody);
        reqMessage.setSignature(sign);
        reqMessage.setSocketBody(originBody + "&signature=" + sign + CommonConstants.TAIL_END);
        String channelId = terminalLogin.getNettyChannelId();

        Channel channel = NettyChannelMap.getChannelByName(channelId);

        // 连接不存在
        if (channel == null || !channel.isActive()) {
            log.error("出现连接为空，终端登录信息terminlogin={}", JSON.toJSONString(terminalLogin));
            loginService.closeTerminlogin(terminalLogin);
            return false;
        }

        Request request = new SocketRequest(reqMessage, RemotingUtil.parseRemoteAddress(channel), channel);
        log.info(">>>>请求终端校验，socketmessage = {}", reqMessage.getSocketBody());


        TransSocketMessage rspMessage = null;
        try {
            Response response = providerClientService.request(request, checkTerminalValidTimeout);
            rspMessage = response.getValue();
        } catch (SocketAbstractException e) {
            log.error("请求终端校验超时(socket异常)，terminalLogin=" + JSON.toJSONString(terminalLogin), e);
            logout(channel);
            return false;
        }

        if (rspMessage == null) {
            log.error("请码校验超时, 未返回值，terminal_id=" + terminalLogin.getTerminalId());
            return false;
        }

        log.info(">>>>接受请码校验，socketmessage = {}", rspMessage.getSocketBody());

        if (!validateSign(rspMessage, terminalLogin, channel)) {
            log.error("请求终端可用性接口，应答报文签名错误...");
            return false;
        }

        String rs = rspMessage.getMessage();
        Map<String, String> ackMap = JSON.parseObject(rs, new TypeReference<Map<String, String>>() {
        });

        //String sessionId = rspMessage.getSessionId();

        String status = ackMap.get("status");
        if (CommonErrors.SUCCESS.getCode().equals(status)) {
            return true;
        } else {
            logout(channel);
            return false;
        }
    }

    private boolean validateSign(TransSocketMessage rspMessage, TerminalLogin terminalLogin, Channel channel) {
        String rspSocketBody = rspMessage.getSocketBody();
        String signIdx = "&signature=";
        if (StringUtils.contains(rspSocketBody, signIdx)) {
            String rspsignBody = StringUtils.substring(rspSocketBody, 0, StringUtils.indexOf(rspSocketBody, signIdx));
            rspsignBody = rspsignBody + "appKey=" + terminalLogin.getAppKey();
            String genMd5 = MD5Sign.md5(rspsignBody);
            if (!StringUtils.equals(genMd5, rspMessage.getSignature())) {
                log.error("请码校验错误(验签失败)，断开连接, terminal_id=" + terminalLogin.getTerminalId());
                logout(channel);
                return false;
            }
        } else {
            log.error("请码校验错误(未签名)，断开连接, terminal_id=" + terminalLogin.getTerminalId());
            logout(channel);
            return false;
        }

        return true;
    }

    private void logout(Channel channel) {
        String nettyChannelId = channel.id().asLongText();
        loginService.logout(channel, nettyChannelId);
        channel.close();
    }

}
